package mobile.game.common.net.netty;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import mobile.game.common.net.Connection;
import mobile.game.common.support.ClassManager;
import mobile.game.core.Port;
import mobile.game.core.PortTask;
import mobile.game.core.support.Utils;
import mobile.game.core.support.log.LogCore;
import mobile.game.core.support.random.RandomUtils;

public class ServerHandler extends ChannelInboundHandlerAdapter {
	// 连接ID临时代替方案
	public static final AtomicInteger idAuto = new AtomicInteger();
	
	// 当前连接信息
	private Connection conn;
	// Connection所在的Port id
	private String connPortId;
	// 用来解密客户端下发的消息
	private RC4 rc4 = null;
	
	/* 建立新连接
	 * @see io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.channel.ChannelHandlerContext)
	 */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		super.channelActive(ctx);
		
		// 当前channel
		Channel channel = ctx.channel();
		
		// 分配一个随机port
		int connId = idAuto.incrementAndGet();
		
		List<Port> list = ClassManager.getMe().getPortByType("ConnPort");
		
		if (list.size() == 0) {
			LogCore.conn.error("当前进程没有启动ConnPort线程，无法处理连接事件");
			return ;
		}
		
		int n = RandomUtils.nextInt(list.size());
		
		Port port = list.get(n);
		
		connPortId = port.getId();
		
		// 初始化连接
		conn = new Connection(channel, connId, connPortId);
		
		// 启动连接
		port.addTask(new ConnectionStart(conn));
		
		// 日志
		if(LogCore.conn.isDebugEnabled()) {
			LogCore.conn.debug("新建一个连接，连接ID={}, 所属Port={}, 本地地址={}, 远程地址={}", 
					conn.getId(), port.getId(), channel.localAddress(), channel.remoteAddress());
		}
		
		// 立即给客户端发送通信密钥
		if(true) {
			sendEncryptKey(channel);
		}
	}
	
	/**
	 * 直接给客户端发送一串消息，用来在连接刚建立时，给客户端发送通信密钥
	 * 由于第一条消息是密钥，密钥本身并没经过加密，所以外挂可以轻易获取到该密钥，
	 * 加密算法又是开源且常见的RC4，能够被轻易的试出来，所以并没有什么卵用，安全性依然无保障
	 * 改进的方式有两种：1通过安全的方式传递密钥，2采用专有的加密算法，总之密钥和算法中至少有一个不让外挂知道即可
	 * @param channel
	 */
	private void sendEncryptKey(Channel channel) {
		byte[] key = RC4.getRandomKey();
		rc4 = new RC4(key);
		
		ByteBuf head = channel.alloc().buffer(4);
		head.writeInt(key.length + 4);
		channel.write(head);
		channel.write(key);
		channel.flush();
	}
	
	/* 连接断开
	 * @see io.netty.channel.ChannelInboundHandlerAdapter#channelInactive(io.netty.channel.ChannelHandlerContext)
	 */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		super.channelInactive(ctx);
		
		if(conn == null) {
			return;
		}
		
		Port port = ClassManager.getMe().getPortById(connPortId);
		
		port.addTask(new ConnectionClose(conn));
		
		// 日志
		if (LogCore.conn.isDebugEnabled()) {
			LogCore.conn.debug("连接关闭：connId={}, status={}", conn.getId(), conn == null ? "" : conn.toString());
		}
	}
	
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		try {
			// 记录数据
			byte[] buff = (byte[]) msg;
			// 超长数据，非法，直接返回
			int len = Utils.bytesToInt(buff, 0);	// 消息长度
			if(len > 64 * 1024) {
				return;
			}
			
			if(true && rc4 != null) {
				rc4.crypt(buff, 4, -1);
			}
			
			conn.onChannelDataComing(buff);
		} catch (Exception e) {
			LogCore.conn.error("", e);
		}
	}
	
	/* 有异常发生
	 * @see io.netty.channel.ChannelInboundHandlerAdapter#exceptionCaught(io.netty.channel.ChannelHandlerContext, java.lang.Throwable)
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//		super.exceptionCaught(ctx, cause);
		
		// 断开连接的日志就不打印堆栈了
		if(cause.getMessage().contains("Connection reset by peer")
				|| cause.getMessage().contains("远程主机强迫关闭了一个现有的连接")) {
//			LogCore.conn.error("发生异常：connId={}, cause={}", conn.getId(), cause.getMessage());
		} else {
			// 输出错误日志
			LogCore.conn.error("发生异常：connId={}, cause={}", conn.getId(), cause);
		}
	}
}

class ConnectionStart extends PortTask {
	private Connection conn;

	public ConnectionStart(Connection conn) {
		super();
		this.conn = conn;
	}

	@Override
	public void execute(Port port) {
		if(conn == null) {
			return;
		}
		
		conn.startup(port);
	}
	
}

class ConnectionClose extends PortTask {
	private Connection conn;

	public ConnectionClose(Connection conn) {
		super();
		this.conn = conn;
	}

	@Override
	public void execute(Port port) {
		if(conn == null) {
			return;
		}
		
		conn.onChannelClosed();
	}
	
}
